/*****************************************************************************************
 *
 *****************************************************************************************/

#include "md5_wrapper.h"

#include <sys/stat.h>
#include <sys/types.h>

#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iostream>

#include "fake_log.h"

namespace MD5Test {

static char *uint8_to_char(const uint8_t *const in_address) {
  if (in_address == nullptr) {
    return nullptr;
  }
  return reinterpret_cast<char *>(const_cast<uint8_t *>(in_address));
}

int32_t Md5Wrapper::calculate_file_md5(const std::string &in_filename, std::string &md5_str, uint64_t total_size) {
  int32_t status_ret = 0;

  if (total_size == 0) {
    struct stat statbuf_obj;
    if (stat(in_filename.c_str(), &statbuf_obj) == 0) {
      total_size = static_cast<uint64_t>(statbuf_obj.st_size);
    } else {
      status_ret = -1;
    }
  }

  if (status_ret == 0) {
    Md5Ctx md5_ctx_obj;
    md5_init(md5_ctx_obj);

    (void)update_file_md5(md5_ctx_obj, in_filename, total_size);

    Md5Result result_obj;
    Md5ResultStr result_str_obj;

    md5_final(result_obj.md5_result_16, md5_ctx_obj);
    md5_print(result_str_obj.result_str, result_obj.md5_result_16, sizeof(result_str_obj.result_str));

    md5_str = uint8_to_char(result_str_obj.result_str);
  }

  return status_ret;
}

int32_t Md5Wrapper::update_file_md5(Md5Ctx &out_md5_ctx, const std::string &in_filename, uint64_t total_size) {
  int32_t status_ret{0};
  std::ifstream in_file(in_filename, std::ios::binary);
  if (in_file.is_open()) {
    char read_buffer[1024]{'\0'};
    uint64_t left{total_size};
    uint64_t read_ret{0};
    while (left != 0) {
      size_t read_size = left < sizeof(read_buffer) ? left : sizeof(read_buffer);
      in_file.read(read_buffer, read_size);
      read_ret = in_file.gcount();
      if (read_ret == 0)  // EOF
        break;
      left -= read_ret;
      md5_update(out_md5_ctx, read_buffer, static_cast<uint64_t>(read_ret));
    }
    in_file.close();
  } else {
    status_ret = -1;
  }
  return status_ret;
}

int32_t Md5Wrapper::calculate_string_md5(const std::string &in_str, std::string &md5_str) {
  return calculate_buf_md5(in_str.c_str(), in_str.size(), md5_str);
}

int32_t Md5Wrapper::calculate_buf_md5(const char *const in_buf, uint64_t total_size, std::string &md5_str) {
  int32_t status_ret = 0;

  if ((in_buf != nullptr) && ((total_size != 0))) {
    Md5Ctx md5_ctx_obj;
    md5_init(md5_ctx_obj);
    md5_update(md5_ctx_obj, in_buf, total_size);

    Md5Result result_obj;
    Md5ResultStr result_str_obj;

    md5_final(result_obj.md5_result_16, md5_ctx_obj);
    md5_print(result_str_obj.result_str, result_obj.md5_result_16, sizeof(result_str_obj.result_str));

    md5_str = uint8_to_char(result_str_obj.result_str);
  } else {
    status_ret = -1;
  }

  return status_ret;
}

int32_t Md5Wrapper::get_md5_from_md5_file(const std::string &in_filename, std::string &md5_str) {
  int32_t status_ret = -1;
  md5_str.clear();
  std::ifstream ifs(in_filename, std::ios::in);
  if (ifs.is_open()) {
    ifs >> md5_str;
    if (ifs) {
      status_ret = 0;
    }
  }
  return status_ret;
}

int32_t Md5Wrapper::check_md5_file(const std::string &in_filename) {
  int32_t status_ret = 0;
  // md5 value
  std::string md5_file_value;
  std::string md5_source_value;

  // md5 file name
  std::string md5_filename;
  md5_filename = in_filename + ".md5";

  // calculate md5 value
  if (calculate_file_md5(in_filename, md5_file_value) < 0) {
    status_ret = -1;
  }

  // get md5 value from file
  if ((status_ret == 0) && (get_md5_from_md5_file(md5_filename, md5_source_value) < 0)) {
    status_ret = -1;
  }

  if ((status_ret == 0) && (md5_file_value.compare(md5_source_value) != 0)) {
    status_ret = -1;
  }
  return status_ret;
}

}  // namespace MD5Test
